/*
 * Copyright (c) 2010-2017 Red Hat, Inc.
 */

#include "pipe.h"
#include "log.h"

PipeServer::PipeServer(const std::wstring &sName)
    : m_sPipeName(sName), m_hPipe(INVALID_HANDLE_VALUE), m_hThread(nullptr), m_buffer(nullptr)
{ }

PipeServer::~PipeServer(void)
{
    if (m_buffer != nullptr) {
        delete[] m_buffer;
        m_buffer = nullptr;
    }
}

void PipeServer::SetData(std::wstring &sData)
{
    if (m_buffer != nullptr) {
        ZeroMemory(&m_buffer[0], DATA_BUFFER_SIZE);
    } else {
        LOGE("{} Not execute memset.", __FUNCTION__);
        return;
    }
    wcsncpy_s(&m_buffer[0], DATA_BUFFER_LENGTH, sData.c_str(),
              __min(DATA_BUFFER_LENGTH - 1, sData.size()));
}

void PipeServer::GetData(std::wstring &sData)
{
    sData.clear();
    sData.append(m_buffer);
}

bool PipeServer::Init()
{
    m_buffer = new wchar_t[DATA_BUFFER_LENGTH];
    if (m_buffer == nullptr) {
        LOGE("Memory Allocation Failed.");
        return false;
    }

    if (m_sPipeName.empty()) {
        LOGE("Error: Invalid pipe name.");
        return false;
    }

    m_hPipe =
        ::CreateNamedPipe(m_sPipeName.c_str(), PIPE_ACCESS_DUPLEX,
                          PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                          PIPE_UNLIMITED_INSTANCES, DATA_BUFFER_SIZE,
                          DATA_BUFFER_SIZE, NMPWAIT_USE_DEFAULT_WAIT, NULL);
    if (INVALID_HANDLE_VALUE == m_hPipe) {
        LOGE("CreateNamedPipe failed with error: {}", GetLastError());
        return false;
    }

    return true;
}

void PipeServer::Run()
{
    if (!WaitForClient()) {
        LOGE("Wait for client failed.");
        return;
    }

    while (Read()) {
        std::wstring command;
        GetData(command);
        if (!command.empty()) {
            if (!_wcsnicmp(CONNECTED, command.c_str(), command.size())) {
            } else if (!_wcsnicmp(CLOSED, command.c_str(), command.size())) {
            } else {
                LOGE(L"Unknown command = {}.", command.c_str());
            }
        }
    }
}

DWORD WINAPI PipeServer::ServerThread(LPVOID ptr)
{
    PipeServer *pServer = reinterpret_cast<PipeServer*>(ptr);
    pServer->Run();
    return 0;
}

bool PipeServer::WaitForClient()
{
    if (!ConnectNamedPipe(m_hPipe, nullptr)) {
        if (ERROR_PIPE_CONNECTED != GetLastError()) {
            LOGE("ConnectNamedPipe failed with error: {}", GetLastError());
            return false;
        }
    }
    return true;
}

void PipeServer::Close()
{
    ::FlushFileBuffers(m_hPipe);
    ::DisconnectNamedPipe(m_hPipe);
    ::CloseHandle(m_hPipe);
    m_hPipe = INVALID_HANDLE_VALUE;
    ::CloseHandle(m_hThread);
    m_hThread = nullptr;
}

bool PipeServer::Read()
{
    DWORD drBytes = 0;
    BOOL bFinishedRead = FALSE;
    int read = 0;
    int left = DATA_BUFFER_SIZE;
    do {
        bFinishedRead = ::ReadFile(m_hPipe, &m_buffer[read], left, &drBytes, nullptr);
        if (!bFinishedRead && ERROR_MORE_DATA != GetLastError()) {
            LOGE("ReadFile failed with error: {}", GetLastError());
            break;
        }

        read += drBytes;
        left -= drBytes;
        if (left <= 0) {
            break;
        }
    } while (!bFinishedRead);

    if (FALSE == bFinishedRead || 0 == drBytes) {
        LOGE("ReadFile failed");
        return false;
    }
    return true;
}

bool PipeServer::Write()
{
    DWORD dwBytes;
    BOOL bResult = ::WriteFile(m_hPipe, m_buffer,
        // 2 Indicates that the end of the reservation '\0'
        (DWORD)(::wcslen(m_buffer) * sizeof(wchar_t) + 2), &dwBytes, nullptr);
    // 2 Indicates that the end of the reservation '\0'
    if (FALSE == bResult || wcslen(m_buffer) * sizeof(wchar_t) + 2 != dwBytes) {
        LOGE("WriteFile failed");
        return false;
    }
    return true;
}

PipeClient::PipeClient(std::wstring &sName)
    : m_sPipeName(sName), m_hPipe(INVALID_HANDLE_VALUE), m_hThread(nullptr), m_buffer(nullptr)
{ }

PipeClient::~PipeClient(void)
{
    if (m_buffer != nullptr) {
        delete[] m_buffer;
        m_buffer = nullptr;
    }
}

void PipeClient::SetData(std::wstring &sData)
{
    if (m_buffer != nullptr) {
        ZeroMemory(&m_buffer[0], DATA_BUFFER_SIZE);
    } else {
        LOGE("{} Not execute memset.", __FUNCTION__);
        return;
    }
    wcsncpy_s(&m_buffer[0], DATA_BUFFER_LENGTH, sData.c_str(),
              __min(DATA_BUFFER_LENGTH - 1, sData.size()));
}

void PipeClient::GetData(std::wstring &sData)
{
    sData.clear();
    sData.append(m_buffer);
}

bool PipeClient::Init()
{
    m_buffer = new wchar_t[DATA_BUFFER_LENGTH];
    if (m_buffer == nullptr) {
        LOGE("Memory Allocation Failed.");
        return false;
    }

    if (m_sPipeName.empty()) {
        return false;
    }

    m_hThread = ::CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)ClientThread, (LPVOID)this, 0, nullptr);
    if (nullptr == m_hThread) {
        LOGE("Cannot create thread with error: {}.", GetLastError());
        return false;
    }
    return true;
}

DWORD WINAPI PipeClient::ClientThread(LPVOID ptr)
{
    PipeClient *pClient = reinterpret_cast<PipeClient *>(ptr);
    pClient->Run();
    return 0;
}

void PipeClient::Run()
{
    if (!ConnectToServer()) {
        LOGE("Failed to Connect server.");
        return;
    }

    while (Read()) {
        std::wstring command;
        GetData(command);
        if (!command.empty()) {
            if (!_wcsnicmp(CLOSE, command.c_str(), command.size())) {
                std::wstring command(CLOSED);
                SetData(command);
                Write();
                return;
            } else {
                LOGE(L"Invalid command = {}.", command.c_str());
            }
        }
    }
}

void PipeClient::WaitRunning()
{
    if (nullptr != m_hThread) {
        WaitForSingleObject(m_hThread, INFINITE);
    }
}

HANDLE PipeClient::GetHandle()
{
    return m_hThread;
}

bool PipeClient::ConnectToServer()
{
    m_hPipe = ::CreateFile(m_sPipeName.c_str(), GENERIC_READ | GENERIC_WRITE,
                           0, nullptr, OPEN_EXISTING, 0, nullptr);
    if (INVALID_HANDLE_VALUE == m_hPipe) {
        LOGE("Could not connect to pipe server.");
        return false;
    }
    return true;
}

void PipeClient::Close()
{
    CancelIoEx(m_hPipe, nullptr);
    ::FlushFileBuffers(m_hPipe);
    ::DisconnectNamedPipe(m_hPipe);
    ::CloseHandle(m_hPipe);
    m_hPipe = INVALID_HANDLE_VALUE;
    ::CloseHandle(m_hThread);
    m_hThread = nullptr;
}

bool PipeClient::Read()
{
    DWORD drBytes = 0;
    BOOL bFinishedRead = FALSE;
    int read = 0;
    int left = DATA_BUFFER_SIZE;
    do {
        bFinishedRead = ::ReadFile(m_hPipe, &m_buffer[read], left,
                                   &drBytes, nullptr);
        if (!bFinishedRead && ERROR_MORE_DATA != GetLastError()) {
            LOGE("ReadFile failed with error: {}", GetLastError());
            break;
        }

        read += drBytes;
        left -= drBytes;
        if (left <= 0) {
            break;
        }
    } while (!bFinishedRead);

    if (FALSE == bFinishedRead || 0 == drBytes) {
        LOGE("ReadFile failed");
        return false;
    }
    return true;
}

bool PipeClient::Write()
{
    DWORD dwBytes;
    BOOL bResult = ::WriteFile(
        // 2 Indicates that the end of the reservation '\0'
        m_hPipe, m_buffer, (DWORD)(::wcslen(m_buffer) * sizeof(wchar_t) + 2),
        &dwBytes, nullptr);
    // 2 Indicates that the end of the reservation '\0'
    if (FALSE == bResult || wcslen(m_buffer) * sizeof(wchar_t) + 2 != dwBytes) {
        LOGE("WriteFile failed");
        return false;
    }

    return true;
}
